﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.IO;

namespace A2DFramework.AOPService
{
    public class ILProxyCreator<T>
        where T : class,new()
    {
        private static Dictionary<Type, Type> proxyMap = new Dictionary<Type, Type>();

        public static T CreateProxy()
        {
            string proxyName = string.Format("{0}_Proxy", typeof(T).FullName);

            if (proxyMap.ContainsKey(typeof(T)))
                return Activator.CreateInstance(proxyMap[typeof(T)]) as T;

            string filePath=System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, proxyName+".dll");
            if (System.IO.File.Exists(filePath))
            {
                Assembly myAm=Assembly.LoadFrom(filePath);
                Type myType = myAm.GetType(proxyName);

                proxyMap[typeof(T)] = myType;

                return Activator.CreateInstance(myType) as T;
            }

            var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(proxyName), AssemblyBuilderAccess.RunAndSave);

            var moduleBldr = asm.DefineDynamicModule(proxyName, proxyName + ".dll");

            var typeBldr = moduleBldr.DefineType(proxyName, TypeAttributes.Public, typeof(T));
            //interceptor
            var fldInterceptor = typeBldr.DefineField("interceptor", typeof(Interceptor), FieldAttributes.Private);
            //construtor
            var constructorBldr = typeBldr.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
            var il = constructorBldr.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);

            il.Emit(OpCodes.Newobj, typeof(Interceptor).GetConstructor(new Type[0]));
            il.Emit(OpCodes.Stfld, fldInterceptor);
            il.Emit(OpCodes.Ret);

            //methods
            var methods = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance);

            for (var i = 0; i < methods.Length; i++)
            {
                var paramTypes = GetParametersType(methods[i]);
                var methodBlfr = typeBldr.DefineMethod(methods[i].Name, MethodAttributes.Public | MethodAttributes.Virtual, CallingConventions.Standard, methods[i].ReturnType, paramTypes);

                il = methodBlfr.GetILGenerator();
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, fldInterceptor);
                il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(new Type[0]));
                il.Emit(OpCodes.Ldstr, methods[i].Name);
                //创建参数数组
                if (paramTypes == null)
                {
                    il.Emit(OpCodes.Ldnull);
                }
                else
                {
                    var parameters = il.DeclareLocal(typeof(object[]));
                    il.Emit(OpCodes.Ldc_I4, paramTypes.Length);
                    il.Emit(OpCodes.Newarr, typeof(object));
                    il.Emit(OpCodes.Stloc, parameters);

                    for (var j = 0; j < paramTypes.Length; j++)
                    {
                        il.Emit(OpCodes.Ldloc, parameters);
                        il.Emit(OpCodes.Ldc_I4, j);
                        il.Emit(OpCodes.Ldarg, j + 1);

                        il.Emit(OpCodes.Stelem_Ref);
                    }
                    il.Emit(OpCodes.Ldloc, parameters);
                }
                il.Emit(OpCodes.Callvirt, typeof(Interceptor).GetMethod("Invoke"));
                if (methods[i].ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Pop);
                }
                il.Emit(OpCodes.Ret);
            }
            var t = typeBldr.CreateType();
            asm.Save(proxyName+".dll");

            proxyMap[typeof(T)] = t;

            return Activator.CreateInstance(t) as T;
        }
        private static Type[] GetParametersType(MethodInfo method)
        {
            Type[] paramTypes = null;

            if (method != null)
            {
                var parameters = method.GetParameters();
                if (parameters.Length > 0)
                {

                    paramTypes = new Type[parameters.Length];

                    for (var i = 0; i < parameters.Length; i++)
                    {
                        paramTypes[i] = parameters[i].ParameterType;
                    }
                }
            }
            return paramTypes;
        }
    }
}